package org.hackystat.sdsa.service.server;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import org.hackystat.sensorbase.client.SensorBaseClient;
import org.restlet.Context;
import org.restlet.Guard;
import org.restlet.data.ChallengeScheme;

/**
 * Performs authentication of each HTTP request using HTTP Basic authentication. Checks
 * user/password credentials by pinging SensorBase, then caching authentic user/password
 * combinations. If a cached user/password combo does not match the current user/password combo,
 * then the SensorBase is pinged again (because maybe the user has changed their password recently).
 * 
 * @author Hongbing Kou
 */
public class Authenticator extends Guard {
  
  /** A map containing previously verified credentials. */
  private Map<String, String> credentials = new HashMap<String, String>();
  
  /** A map containing SensorBaseClient instances, one per credentialled user. */
  private Map<String, SensorBaseClient> sensorbaseMap = 
    new HashMap<String, SensorBaseClient>();
 
  /** The sensorbase host, such as "http://localhost:9876/sensorbase/" */
  private String sensorBaseHost;

  /** The key to be used to retrieve the SensorbaseClient map from the server context. */
  public static final String AUTHENTICATOR_SENSORBASE_CLIENTS_KEY = 
    "authenticator.sensorbase.clients";
  /** Server logger object. */
  private Logger logger;

  /**
   * Initializes this Guard to do HTTP Basic authentication.
   * Puts the credentials map in the server context so that Resources can get the 
   * password associated with the uriUser for their own invocations to the SensorBase. 
   * @param context The server context.
   * @param sensorBaseHost The host, such as 'http://localhost:9876/sensorbase/'.
   * @param logger Logger.
   */
  public Authenticator (Context context, String sensorBaseHost, Logger logger) {
    super(context, ChallengeScheme.HTTP_BASIC,  "SDSA");
    this.sensorBaseHost = sensorBaseHost;
    this.logger = logger;
    context.getAttributes().put(AUTHENTICATOR_SENSORBASE_CLIENTS_KEY, sensorbaseMap);
}
  
  /**
   * Returns true if the passed credentials are OK.
   * @param identifier The account name.
   * @param secretCharArray The password. 
   * @return If the credentials are valid.
   */
  @Override protected boolean checkSecret(String identifier, char[] secretCharArray) {
    String secret = new String(secretCharArray);
    // Return true if the user/password credentials are in the cache. 
    if (credentials.containsKey(identifier) &&
        secret.equals(credentials.get(identifier))) {
      return true;
    }
    // Otherwise we check the credentials with the SensorBase.
    boolean isRegistered = SensorBaseClient.isRegistered(sensorBaseHost, identifier, secret);
    if (isRegistered) {
      // Credentials are good, so save them and create clients for this user. 
      credentials.put(identifier, secret);
      // Only create a new client if there is no old one. This is intended to facilitate caching,
      // but has the problem that there's no way to get rid of bogus entries besides restarting
      // this service.  Something to worry about. 
      if (!sensorbaseMap.containsKey(identifier)) {
        sensorbaseMap.put(identifier, new SensorBaseClient(sensorBaseHost, identifier, secret));
      }
    }
    else {
      logger.warning("Authentication failure: either user name or password is incorrect.");
    }
    return isRegistered;
  }
}
